<template>
	<view class="nut-elevator">
		<scroll-view
			ref="listview"
			class="nut-elevator__list nut-elevator__list--mini"
			:scroll-top="state.scrollTop"
			:scroll-y="true"
			:scroll-with-animation="true"
			:scroll-anchoring="true"
			:style="{ height: isNaN(+height) ? height : `${height}px` }"
			@scroll="listViewScroll"
		>
			<view
				v-for="(item, index) in indexList"
				:key="item[acceptKey]"
				:ref="setListGroup"
				:class="[
					'nut-elevator__list__item',
					`elevator__item__${index}`,
				]"
			>
				<view class="nut-elevator__list__item__code">{{
					item[acceptKey]
				}}</view>
				<view
					v-for="subitem in item.list"
					:key="subitem['id']"
					class="nut-elevator__list__item__name"
					:class="{
						'nut-elevator__list__item__name--highcolor':
							state.currentData.id === subitem.id &&
							state.currentKey === item[acceptKey],
					}"
					@tap="handleClickItem(item[acceptKey], subitem)"
				>
					<span v-if="!$slots.default" v-html="subitem.name"></span>
					<slot v-else :item="subitem"></slot>
				</view>
			</view>
		</scroll-view>
		<view
			v-show="state.scrollY > 2 && isSticky"
			class="nut-elevator__list__fixed"
		>
			<view
				v-if="indexList[state.currentIndex]"
				class="nut-elevator__list__fixed-title"
				>{{ props.indexList[state.currentIndex][acceptKey] }}</view
			>
		</view>
		<view
			v-show="state.scrollStart"
			v-if="indexList.length > 0 && indexList[state.codeIndex]"
			class="nut-elevator__code--current"
		>
			{{ indexList[state.codeIndex][acceptKey] }}
		</view>
		<view
			class="nut-elevator__bars"
			@touchstart="touchStart"
			@touchmove.stop.prevent="touchMove"
			@touchend="touchEnd"
		>
			<view
				class="nut-elevator__bars__inner"
				v-if="indexList[state.currentIndex]"
			>
				<view
					v-for="(item, index) in indexList"
					:key="item[acceptKey]"
					class="nut-elevator__bars__inner__item"
					:class="{
						active:
							item[acceptKey] ===
							indexList[state.currentIndex][acceptKey],
					}"
					:data-index="index"
					@tap="handleClickIndex(item[acceptKey])"
				>
					{{ item[acceptKey] }}
				</view>
			</view>
		</view>
	</view>
</template>

<script setup lang="ts">
import '@nutui/nutui-taro/dist/packages/elevator/index.scss';
import {
	reactive,
	nextTick,
	ref,
	type Ref,
	watch,
	defineProps,
	defineEmits,
	defineExpose,
	type PropType,
} from 'vue';
import Taro from '@tarojs/taro';

const props = defineProps({
	height: {
		type: [Number, String],
		default: '200px',
	},
	acceptKey: {
		type: String,
		default: 'title',
	},
	indexList: {
		type: Array as PropType<any[]>,
		default: () => [],
	},
	isSticky: {
		type: Boolean,
		default: false,
	},
	spaceHeight: {
		type: Number,
		default: 23,
	},
	titleHeight: {
		type: Number,
		default: 35,
	},
});

const emit = defineEmits(['clickItem', 'clickIndex', 'change']);

const spaceHeight = 23;
const listview: Ref<HTMLElement> = ref() as Ref<HTMLElement>;
const state = reactive({
	anchorIndex: 0,
	codeIndex: 0,
	listHeight: [] as number[],
	listGroup: [] as HTMLLIElement[],
	touchState: {
		y1: 0,
		y2: 0,
	},
	scrollStart: false,
	currentIndex: 0,
	query: Taro.createSelectorQuery(),
	scrollTop: 0,
	currentData: {} as any,
	currentKey: '',
	scrollY: 0,
});

const getData = (el: HTMLElement): string | void => {
	if (!el.dataset.index) {
		return '0';
	}
	return el.dataset.index as string;
};

const setListGroup = (el: any) => {
	nextTick(() => {
		if (!state.listGroup.includes(el) && el != null) {
			state.listGroup.push(el);
		}
	});
};

// const calculateHeight = () => {
// 	state.listHeight = [];
// 	let height = 0;
// 	state.listHeight.push(height);
// 	for (let i = 0; i < state.listGroup.length; i++) {
// 		state.query.selectAll(`.elevator__item__${i}`).boundingClientRect();
// 		state.query.exec((res) => {
// 			height += Math.floor(res[i][0].height);
// 			state.listHeight.push(height);
// 		});
// 	}
// };

const calculateHeight = () => {
	state.listHeight = [0];
	let currentHeight = 0;
	const query = Taro.createSelectorQuery();
	state.listGroup.forEach((_, index) => {
		query.selectAll(`.elevator__item__${index}`).boundingClientRect();
	});
	query.exec((res: any) => {
		if (!res || !res.length) {
			return;
		}
		res.forEach((rectList: any[], index: number) => {
			if (!rectList || !rectList.length) {
				// console.warn(`[Elevator] No rects at index ${index}`);
				return;
			}
			rectList.forEach((rect) => {
				if (rect && rect.height) {
					currentHeight += Math.floor(rect.height);
				}
			});
			state.listHeight[index + 1] = currentHeight;
		});
	});
};
const scrollTo = (index: number) => {
	if (!index && index !== 0) {
		return;
	}
	if (index < 0) index = 0;
	if (index > state.listHeight.length - 2)
		index = state.listHeight.length - 2;
	state.codeIndex = index;
	state.scrollTop = state.listHeight[index];
};

const touchStart = (e: TouchEvent) => {
	state.scrollStart = true;
	let index = getData(e.target as HTMLElement);
	let firstTouch = e.touches[0];
	state.touchState.y1 = firstTouch.pageY;
	state.anchorIndex = +index;
	state.codeIndex = +index;
	scrollTo(+index);
};

const touchMove = (e: TouchEvent) => {
	let firstTouch = e.touches[0];
	state.touchState.y2 = firstTouch.pageY;
	let delta = ((state.touchState.y2 - state.touchState.y1) / spaceHeight) | 0;
	state.codeIndex = state.anchorIndex + delta;
	scrollTo(state.currentIndex);
};

const touchEnd = () => {
	state.scrollStart = false;
};

const handleClickItem = (key: string, item: any) => {
	emit('clickItem', key, item);
	state.currentData = item;
	state.currentKey = key;
};

const handleClickIndex = (key: string) => {
	emit('clickIndex', key);
};

const listViewScroll = (e: Event) => {
	let target = e.target as Element;
	let scrollTop = target.scrollTop;
	const listHeight = state.listHeight;
	state.scrollY = Math.floor(scrollTop);
	for (let i = 0; i < listHeight.length - 1; i++) {
		let height1 = listHeight[i];
		let height2 = listHeight[i + 1];
		if (state.scrollY >= height1 && state.scrollY < height2) {
			state.currentIndex = i;
			return;
		}
	}
};

// 监听效果
watch(
	() => state.listGroup.length,
	() => {
		Taro.nextTick(calculateHeight);
	}
);

watch(
	() => state.currentIndex,
	(newVal: number) => {
		emit('change', newVal);
	}
);

// 暴露方法
defineExpose({
	scrollTo,
});
</script>
